package org.tamal.classinfo;

import java.io.DataInputStream;
import java.io.IOException;

/**
 * This class represents Constant Pool Structure.
 * @author Tamal Kanti Nath
 */
public final class Constant {
    /** CONSTANT_Utf8. */
    public static final int CONSTANT_UTF8 = 1;
    /** CONSTANT_Integer. */
    public static final int CONSTANT_INTEGER = 3;
    /** CONSTANT_Float. */
    public static final int CONSTANT_FLOAT = 4;
    /** CONSTANT_Long. */
    public static final int CONSTANT_LONG = 5;
    /** CONSTANT_Double. */
    public static final int CONSTANT_DOUBLE = 6;
    /** CONSTANT_Class. */
    public static final int CONSTANT_CLASS = 7;
    /** CONSTANT_String. */
    public static final int CONSTANT_STRING = 8;
    /** CONSTANT_Fieldref. */
    public static final int CONSTANT_FIELD_REF = 9;
    /** CONSTANT_Methodref. */
    public static final int CONSTANT_METHOD_REF = 10;
    /** CONSTANT_InterfaceMethodref. */
    public static final int CONSTANT_INTERFACE_METHOD_REF = 11;
    /** CONSTANT_NameAndType. */
    public static final int CONSTANT_NAME_TYPE = 12;
    /** CONSTANT_MethodHandle. */
    public static final int CONSTANT_METHOD_HANDLE = 15;
    /** CONSTANT_MethodType. */
    public static final int CONSTANT_METHOD_TYPE = 16;
    /** CONSTANT_InvokeDynamic. */
    public static final int CONSTANT_INVOKE_DYNMIC = 18;
    /** REF_getField. */
    public static final int REF_GET_FIELD = 1;
    /** REF_getStatic. */
    public static final int REF_GET_STATIC = 2;
    /** REF_putField. */
    public static final int REF_PUT_FIELD = 3;
    /** REF_putStatic. */
    public static final int REF_PUT_STATIC = 4;
    /** REF_invokeVirtual. */
    public static final int REF_INVOKE_VIRTUAL = 5;
    /** REF_invokeStatic. */
    public static final int REF_INVOKE_STATIC = 6;
    /** REF_invokeSpecial. */
    public static final int REF_INVOKE_SPECIAL = 7;
    /** REF_newInvokeSpecial. */
    public static final int REF_NEW_INVOKE_SPECIAL = 8;
    /** REF_invokeInterface. */
    public static final int REF_INVOKE_INTERFACE = 9;

    private Constant[] pool;

    private final int tag;
    private Integer nameIndex;
    private Integer classIndex;
    private Integer nameTypeIndex;
    private Integer stringIndex;
    private Integer descriptorIndex;
    private Integer referenceKind;
    private Integer referenceIndex;
    private Integer bootstrapIndex;
    private Integer integerData;
    private Float floatData;
    private Long longData;
    private Double doubleData;
    private String stringData;

    private Constant(DataInputStream in, Constant[] pool) throws IOException {
        tag = in.readUnsignedByte();
        switch (tag) {
        case CONSTANT_UTF8:
            stringData = in.readUTF();
            break;
        case CONSTANT_INTEGER:
            integerData = in.readInt();
            break;
        case CONSTANT_FLOAT:
            floatData = in.readFloat();
            break;
        case CONSTANT_LONG:
            longData = in.readLong();
            break;
        case CONSTANT_DOUBLE:
            doubleData = in.readDouble();
            break;
        case CONSTANT_CLASS:
            nameIndex = in.readUnsignedShort();
            break;
        case CONSTANT_STRING:
            stringIndex = in.readUnsignedShort();
            break;
        case CONSTANT_FIELD_REF:
        case CONSTANT_METHOD_REF:
        case CONSTANT_INTERFACE_METHOD_REF:
            classIndex = in.readUnsignedShort();
            nameTypeIndex = in.readUnsignedShort();
            break;
        case CONSTANT_NAME_TYPE:
            nameIndex = in.readUnsignedShort();
            descriptorIndex = in.readUnsignedShort();
            break;
        case CONSTANT_METHOD_HANDLE:
            referenceKind = in.readUnsignedByte();
            referenceIndex = in.readUnsignedShort();
            break;
        case CONSTANT_METHOD_TYPE:
            descriptorIndex = in.readUnsignedShort();
            break;
        case CONSTANT_INVOKE_DYNMIC:
            bootstrapIndex = in.readUnsignedShort();
            nameTypeIndex = in.readUnsignedShort();
            break;
        default:
            throw new ClassFormatException("Invalid Constant Pool tag: " + tag);
        }
        this.pool = pool;
    }

    /**
     * Returns nameIndex.
     * @return the nameIndex
     */
    public int getTag() {
        return tag;
    }

    /**
     * Returns nameIndex.
     * @return the nameIndex
     */
    public Integer getNameIndex() {
        return nameIndex;
    }

    /**
     * Returns classIndex.
     * @return the classIndex
     */
    public Integer getClassIndex() {
        return classIndex;
    }

    /**
     * Returns nameTypeIndex.
     * @return the nameTypeIndex
     */
    public Integer getNameTypeIndex() {
        return nameTypeIndex;
    }

    /**
     * Returns stringIndex.
     * @return the stringIndex
     */
    public Integer getStringIndex() {
        return stringIndex;
    }

    /**
     * Returns descriptorIndex.
     * @return the descriptorIndex
     */
    public Integer getDescriptorIndex() {
        return descriptorIndex;
    }

    /**
     * Returns referenceKind.
     * @return the referenceKind
     */
    public Integer getReferenceKind() {
        return referenceKind;
    }

    /**
     * Returns referenceIndex.
     * @return the referenceIndex
     */
    public Integer getReferenceIndex() {
        return referenceIndex;
    }

    /**
     * Returns bootstrapIndex.
     * @return the bootstrapIndex
     */
    public Integer getBootstrapIndex() {
        return bootstrapIndex;
    }

    /**
     * Returns integerData.
     * @return the integerData
     */
    public Integer getIntegerData() {
        return integerData;
    }

    /**
     * Returns floatData.
     * @return the floatData
     */
    public Float getFloatData() {
        return floatData;
    }

    /**
     * Returns longData.
     * @return the longData
     */
    public Long getLongData() {
        return longData;
    }

    /**
     * Returns doubleData.
     * @return the doubleData
     */
    public Double getDoubleData() {
        return doubleData;
    }

    /**
     * Returns stringData.
     * @return the stringData
     */
    public String getStringData() {
        return stringData;
    }

    @Override
    public String toString() {
        String str;
        switch (tag) {
        case CONSTANT_UTF8:
            str = stringData;
            break;
        case CONSTANT_INTEGER:
            str = String.valueOf(integerData);
            break;
        case CONSTANT_FLOAT:
            str = String.valueOf(floatData);
            break;
        case CONSTANT_LONG:
            str = String.valueOf(longData);
            break;
        case CONSTANT_DOUBLE:
            str = String.valueOf(doubleData);
            break;
        case CONSTANT_CLASS:
            str = pool[nameIndex].stringData.replace('/', '.');
            break;
        case CONSTANT_STRING:
            str = pool[stringIndex].stringData;
            break;
        case CONSTANT_FIELD_REF:
        case CONSTANT_METHOD_REF:
        case CONSTANT_INTERFACE_METHOD_REF:
            str = Util.insertClass(pool[classIndex].toString(),
                pool[nameTypeIndex].toString());
            break;
        case CONSTANT_NAME_TYPE:
            str = Util.formatNameType(pool[nameIndex].stringData,
                pool[descriptorIndex].stringData.replace('/', '.'));
            break;
        case CONSTANT_METHOD_HANDLE:
            str = pool[referenceIndex].toString();
            break;
        case CONSTANT_METHOD_TYPE:
            str = pool[descriptorIndex].stringData;
            break;
        case CONSTANT_INVOKE_DYNMIC:
            str = pool[nameTypeIndex].toString() + " (*)";
            break;
        default:
            str = "Tag: " + tag;
        }
        return str;
    }

    /**
     * Static method to read constant pool from a class.
     * @param in The input stream for a class.
     * @return an array of ConstantPool.
     * @throws IOException if any IO exception occurs
     * @see ClassInfo
     */
    static Constant[] readConstantPool(DataInputStream in) throws IOException {
        Constant[] pool = new Constant[in.readUnsignedShort()];
        int i = 1;
        /*
         * All 8-byte constants take up two entries in the constant_pool table
         * of the class file. If a CONSTANT_Long_info or CONSTANT_Double_info
         * structure is the item in the constant_pool table at index n, then the
         * next usable item in the pool is located at index n+2. The
         * constant_pool index n+1 must be valid but is considered unusable.
         */
        while (i < pool.length) {
            pool[i] = new Constant(in, pool);
            if (pool[i].tag == CONSTANT_LONG
                || pool[i].tag == CONSTANT_DOUBLE) {
                i += 2;
            } else {
                ++i;
            }
        }
        return pool;
    }
}
